package com.db.stockexchange.domain;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Logger;

import com.db.stockexchange.domain.order.Order;
import com.db.stockexchange.domain.order.OrderSide;
import com.db.stockexchange.domain.order.OrderType;


public class OrderBook {
	private Logger logger = Logger.getLogger(OrderBook.class.getName());
	
	private final TreeMap<BigDecimal, Queue<Order>> bids = new TreeMap<>(Collections.reverseOrder());
	private final TreeMap<BigDecimal, Queue<Order>> asks = new TreeMap<>();
	
	public List<ExecutionReport> placeOrder(Order order) {
		List<ExecutionReport> reports = new LinkedList<>();
		
		logger.info("new order: " + order);
		
		TreeMap<BigDecimal, Queue<Order>> lookupMap;
		lookupMap = order.getSide() == OrderSide.BUY ? asks : bids;
		
		synchronized (lookupMap) {
			SortedMap<BigDecimal, Queue<Order>> lookupSubmap;
			if (order.getOrderType() == OrderType.LIMIT)
				lookupSubmap = lookupMap.headMap(order.getPrice(), true);
			else
				lookupSubmap = lookupMap;
			
			for (Queue<Order> orders : lookupSubmap.values()) {

				Order matchingOrder;
				while ( (matchingOrder = orders.peek()) != null ) {
					
					if (!matchingOrder.getConnection().alive()) {
						orders.poll();
						continue;
					}
					
					logger.info("found match " + matchingOrder);
					
					int minQty = Math.min(order.getResidualQuantity(), matchingOrder.getResidualQuantity());
					
					order.fill(minQty);
					reports.add(new ExecutionReport(order, matchingOrder));
					matchingOrder.fill(minQty);
					reports.add(new ExecutionReport(matchingOrder, order));
					
					if (matchingOrder.getResidualQuantity() == 0)
						orders.poll();
					if (order.getResidualQuantity() == 0)
						return reports;
				}
			}
		}
		
		// if failed to filled market order then reject it
		if (order.getOrderType() == OrderType.MARKET) {
			order.reject();
			reports.add(new ExecutionReport(order, "market orders storing not supported"));
			return reports;
		}
		
		SortedMap<BigDecimal, Queue<Order>> targetMap;
		targetMap = order.getSide() == OrderSide.BUY ? bids : asks;

		synchronized (targetMap) {
			if (!targetMap.containsKey(order.getPrice())) {
				targetMap.put(order.getPrice(), new LinkedList<Order>());
			}
			
			Queue<Order> ordersQueue = targetMap.get(order.getPrice());
			ordersQueue.add(order);
			logger.info("put to order book " + order);
		}
		
		return reports;
	}
}
